docs(openapi): Autofix OpenAPI spec validation errors#2403
docs(openapi): Autofix OpenAPI spec validation errors#2403
Conversation
…e, and record response schema
Error: WARN Response OpenAPI validation error {"url":"/v2/acts/{actId}","method":"GET","errors":[{"message":"must match exactly one schema in oneOf","path":"/response/data/pricingInfos/0"}]}
Files: FreeActorPricingInfo.yaml:9, PayPerEventActorPricingInfo.yaml:10, PricePerDatasetItemActorPricingInfo.yaml:10, FlatPricePerMonthActorPricingInfo.yaml:10
Root cause: Each pricing info schema referenced the shared PricingModel enum containing all four values (FREE, PAY_PER_EVENT, PRICE_PER_DATASET_ITEM, FLAT_PRICE_PER_MONTH). This caused FreeActorPricingInfo to match any pricing model since it only requires pricingModel and common fields, violating the oneOf constraint which requires exactly one schema to match.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/types/src/paid_actors.ts#L1
Error: WARN Response OpenAPI validation error {"url":"/v2/acts/{actId}/versions/0.0","method":"DELETE","errors":[{"message":"must be equal to one of the allowed values: actor-not-found","path":"/response/error/type"},{"message":"must be equal to one of the allowed values: record-not-found","path":"/response/error/type"},{"message":"must match exactly one schema in oneOf","path":"/response"}]}
Files: acts@{actorId}@versions@{versionNumber}.yaml:75
Root cause: The 404 response for DELETE version used ActorNotFoundError (enum: actor-not-found) but the API actually returns record-or-token-not-found when the actor is not found or access is denied.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/lib/actors.ts#L135
Error: WARN Response OpenAPI validation error {"url":"/v2/actor-runs/{runId}/resurrect","method":"POST","errors":[{"message":"must be integer","path":"/response/data/options/maxItems"}]}
Files: RunOptions.yaml:26
Root cause: The maxItems field in RunOptions was defined as type integer but the API can return null when maxItems is not set. The TypeScript type is maxItems?: number which allows the value to be absent or stored as null in MongoDB.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/types/src/actor.ts#L485
Error: WARN Response OpenAPI validation error {"url":"/v2/key-value-stores/{storeId}/records/{recordKey}?attachment=true","method":"GET","errors":[{"message":"must match exactly one schema in oneOf","path":"/response"}]}
Files: RecordResponse.yaml:5
Root cause: RecordResponse was defined as type object with additionalProperties but KV store records can contain any JSON value type (array, string, number, boolean, null), not just objects. Removing the type constraint allows the schema to accept any stored JSON value.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/lib/key_value_stores.ts#L442
Error: WARN Response OpenAPI validation error {"url":"/v2/key-value-stores/{storeId}/records/{recordKey}?attachment=true","method":"GET","errors":[{"message":"must match exactly one schema in oneOf","path":"/response"}]}
Files: key-value-stores@{storeId}@records@{recordKey}.yaml:41
Root cause: The 200 response defined both application/json (with RecordResponse schema) and */* (with empty schema) content types. The validator creates an internal oneOf between content types, and */* overlaps with application/json causing ambiguous matching. Since KV store records can contain any content type (HTML, text, binary, JSON of any shape), using only */* with an empty schema correctly represents the endpoint behavior without content type ambiguity.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/lib/key_value_stores.ts#L549
|
Preview for this PR was built for commit |
…emove URI format from request URL
Revert: KV store record GET content type removal (false positive - oneOf at /response level is a validator bug)
Error: WARN Response OpenAPI validation error {"url":"/v2/acts","method":"POST","errors":[{"message":"must have required property 'format'"}]}
Files: components/schemas/actors/SourceCodeFile.yaml:3
Root cause: SourceCodeFile schema required format and content, but backend schema marks both as optional with defaults
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/actor/src/actors/actors.both.ts#L272
Error: WARN Response OpenAPI validation error {"url":"/v2/request-queues/{queueId}/head/lock","errors":[{"message":"must match format \"uri\""}]}
Files: components/schemas/request-queues/SharedProperties.yaml:18
Root cause: RequestUrl had format: uri but backend does not validate URLs as URIs
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/schemas/src/request_queues.ts#L27
|
Preview for this PR was built for commit |
…tion, and type constraints
Error: unsupported media type application/x-www-form-urlencoded at /v2/acts/{actorId}/runs and 5 other run endpoints
Files: paths/actors/acts@{actorId}@runs.yaml:114, paths/actors/acts@{actorId}@run-sync.yaml:40, paths/actors/acts@{actorId}@run-sync-get-dataset-items.yaml:63, paths/actor-tasks/actor-tasks@{actorTaskId}@runs.yaml:105, paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync.yaml:40, paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync-get-dataset-items.yaml:63
Root cause: These endpoints use BODY_PARSER_TYPES.raw which accepts any content type via type: () => true. The spec only declared application/json but needs wildcard */* to match actual API behavior.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/middleware/body_parser.ts#L61
Error: must have required property 'name' at POST /v2/actor-tasks
Files: components/schemas/actor-tasks/CreateTaskRequest.yaml:3
Root cause: Backend auto-generates task name if not provided (createActorTask generates name from actor name + random suffix).
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/actor-server/src/actor_tasks/actor_tasks.server.ts#L417
Error: Unknown query parameter 'waitForFinish' at GET /v2/acts/{actorId}/runs/last
Files: paths/actors/acts@{actorId}@Runs@last.yaml:63
Root cause: The runs/last endpoint supports the same parameters as the run object endpoint, including waitForFinish.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/routes_config.ts#L369
Error: must match format "uri" at POST /v2/webhooks for requestUrl
Files: components/schemas/webhooks/WebhookCreate.yaml:29
Root cause: Backend validates webhook requestUrl using custom WEBHOOK_REQUEST_URL_REGEXP (HTTP_URL_REGEX | LOCALHOST_URL_REGEXP), not strict URI format.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/consts/src/webhooks.ts#L31
Error: must be object,null for payload at POST /v2/request-queues/{queueId}/requests/batch
Files: components/schemas/request-queues/RequestBase.yaml:18
Root cause: Backend request queue schema defines payload as SimpleSchema.oneOf(String, Object) on client side, accepting both string and object types.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/schemas/src/request_queues.ts#L40
Error: Parameter 'status' must be url encoded - reserved characters at GET /v2/acts/{actorId}/runs
Files: components/parameters/runAndBuildParameters.yaml:254
Root cause: API accepts comma-separated status values (e.g. ?status=SUCCEEDED,ABORTED). Changed to array type with explode:false to properly model comma-separated serialization.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/actors/act_run_list.ts#L1
Error: Parameter 'filter' must be url encoded - reserved characters at GET /v2/request-queues/{queueId}/requests
Files: paths/request-queues/request-queues@{queueId}@requests.yaml:38
Root cause: API accepts comma-separated filter values (e.g. ?filter=pending,locked). Changed to array type with explode:false to properly model comma-separated serialization.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/request_queues/request_queue_request_list.ts#L1
Error: Parameter 'startedAfter' must be url encoded - reserved characters at GET /v2/acts/{actorId}/runs
Files: components/parameters/runAndBuildParameters.yaml:266
Root cause: ISO 8601 datetime strings contain colons which are reserved characters. Added allowReserved:true since the API accepts unencoded datetime strings.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/actors/act_run_list.ts#L1
|
Preview for this PR was built for commit |
…y to Content-Encoding enum
Error: Unknown query parameter 'waitForFinish' at GET /v2/actor-tasks/{actorTaskId}/runs/last
Files: paths/actor-tasks/actor-tasks@{actorTaskId}@Runs@last.yaml:55
Root cause: The actor-tasks runs/last endpoint forwards all query parameters (including waitForFinish) to the actual run endpoint via routeToLastRunRoutes(), same as the actors runs/last endpoint.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/actor_tasks/last_run.ts#L1
Error: must be equal to one of the allowed values: gzip, deflate, br at PUT /v2/key-value-stores/{storeId}/records/{recordKey}
Files: components/objects/key-value-stores/key-value-stores@{storeId}@records@{recordKey}put.yaml:13
Root cause: The API accepts identity encoding for HTML records (checked at record.ts:102 against [...RECORD_SUPPORTED_ENCODING_TYPES_LIST, IDENTITY_ENCODING_TYPE]). The identity value was missing from the spec's Content-Encoding enum.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/key_value_stores/record.ts#L102
|
Preview for this PR was built for commit |
…s endpoints
Error: Unknown query parameter 'view' at POST /v2/acts/{actorId}/run-sync-get-dataset-items and POST /v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-items
Files: paths/actors/acts@{actorId}@run-sync-get-dataset-items.yaml, paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync-get-dataset-items.yaml
Root cause: The run-sync-get-dataset-items endpoints call respondWithDatasetItemsStream() which processes the view query parameter, but the spec was missing this parameter. Also extracted the inline view parameter from datasets@{datasetId}@items.yaml into a reusable component in datasetItemsParameters.yaml.
Reference: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/api/src/routes/actors/run_sync_dataset.ts#L57
|
Preview for this PR was built for commit |
|
Preview for this PR was built for commit |
|
A PR to update the Python client models has been created: apify/apify-client-python#707 This was automatically triggered by OpenAPI specification changes in this PR. |
apify-api/openapi/components/schemas/actor-pricing-info/PayPerEventActorPricingInfo.yaml
Outdated
Show resolved
Hide resolved
|
Preview for this PR was built for commit |
|
The Python client model PR has been updated with the latest OpenAPI spec changes: apify/apify-client-python#707 |
Per reviewer feedback and updated AGENTS.md syntax hints, replace all
single-item enum definitions with const (OpenAPI 3.1 syntax).
Files:
- components/schemas/actor-pricing-info/PayPerEventActorPricingInfo.yaml:11
- components/schemas/actor-pricing-info/FreeActorPricingInfo.yaml:10
- components/schemas/actor-pricing-info/PricePerDatasetItemActorPricingInfo.yaml:12
- components/schemas/actor-pricing-info/FlatPricePerMonthActorPricingInfo.yaml:12
- paths/request-queues/request-queues@{queueId}@requests@batch.yaml:95
|
Preview for this PR was built for commit |
|
The Python client model PR has been updated with the latest OpenAPI spec changes: apify/apify-client-python#707 |
|
Preview for this PR was built for commit |
|
The Python client model PR has been updated with the latest OpenAPI spec changes: apify/apify-client-python#707 |
|
|
||
| RequestUrl: | ||
| type: string | ||
| format: uri |
There was a problem hiding this comment.
why are we removing these format constraints?
There was a problem hiding this comment.
This constraint does not exist in API:

https://github.com/apify/apify-core/blob/e8976db3b5b2476d2f01af1a84d4268fc61e839f/src/packages/schemas/src/request_queues.ts#L27
There was a problem hiding this comment.
Probably because you might also want to save PseudoURLs to the queue, not just regular URLs. It's been like this since the original implementation: https://github.com/apify/apify-core/commit/9bb347bd4baf62a23bda7c180ce58dd68cf30252#diff-48532f504598f9f0dc62bb5d060e1b3951b95c959d76e797f24e1e6bb155ddebR117
There was a problem hiding this comment.
oh hot damn... so will the API really accept any string?
apify-api/openapi/components/schemas/actor-pricing-info/FlatPricePerMonthActorPricingInfo.yaml
Show resolved
Hide resolved
|
Preview for this PR was built for commit |
|
The Python client model PR has been updated with the latest OpenAPI spec changes: apify/apify-client-python#707 |
janbuchar
left a comment
There was a problem hiding this comment.
LGTM, provided that we resolve the uri format uncertainty
|
|
||
| RequestUrl: | ||
| type: string | ||
| format: uri |
Summary
Autogenerated OpenAPI fixes suggestions based on validation errors generated from running API integration tests with OpenAPI validator turned on.
Error log: https://apify-pr-test-env-logs.s3.us-east-1.amazonaws.com/apify/apify-core/26280/api-e8976db3b5b2476d2f01af1a84d4268fc61e839f.log
apify-core version: https://github.com/apify/apify-core/tree/e8976db3b5b2476d2f01af1a84d4268fc61e839f
Stop reason: No new fixes possible. All remaining validation errors are deliberate negative tests, known false positives (nullable date-time fields), validator limitations (oneOf for KV store records), missing endpoints (out of scope), or global middleware artifacts (method query parameter override). 16 unique error types fixed out of 20 target.
Detailed changes description
Error fixes
1. Fix pricingInfo oneOf validation errors
components/schemas/actor-pricing-info/FreeActorPricingInfo.yaml,PayPerEventActorPricingInfo.yaml,PricePerDatasetItemActorPricingInfo.yaml,FlatPricePerMonthActorPricingInfo.yamlmust have required property 'pricePerUnitUsd'/must have required property 'pricingPerEvent'/must have required property 'trialMinutes'/must have required property 'unitName'at GET/PUT/v2/acts/{actorId}2. Fix maxItems type in RunOptions
components/schemas/actor-runs/RunOptions.yaml:26must be integerat response path/response/data/options/maxItemsintegerbut the API returnsnullwhen not set. Changed totype: [integer, "null"].3. Fix version DELETE 404 error type
paths/actors/acts@{actorId}@versions@{versionNumber}.yaml:72must be equal to one of the allowed values: record-not-foundat DELETE/v2/acts/{actorId}/versions/{versionNumber}ActorNotFoundErrorbut the API returnsRecordOrTokenNotFoundErrorwhen deleting a non-existent version.4. Fix sourceFiles required fields
components/schemas/actors/SourceCodeFile.yamlmust have required property 'folder'/must have required property 'format'at response path/response/data/versions/0/sourceFiles/0formatandfolderwere marked as required in the schema but are optional fields in the API response - they can be missing from individual source files.5. Fix RequestUrl format:uri on SharedProperties
components/schemas/request-queues/SharedProperties.yamlmust match format "uri"at response path for request URL fieldformat: uriconstraint was too strict - the API accepts and returns URLs that may not pass strict URI validation (e.g., URLs with special characters). Removedformat: uri.6. Fix unsupported media type errors on run endpoints
paths/actors/acts@{actorId}@runs.yaml,acts@{actorId}@run-sync.yaml,acts@{actorId}@run-sync-get-dataset-items.yaml,paths/actor-tasks/actor-tasks@{actorTaskId}@runs.yaml,actor-tasks@{actorTaskId}@run-sync.yaml,actor-tasks@{actorTaskId}@run-sync-get-dataset-items.yamlunsupported media type undefinedat POST on all 6 run endpointsBODY_PARSER_TYPES.rawwithtype: () => truewhich accepts ANY content type. The spec only declaredapplication/jsoncontent type. Added"*/*": schema: {}wildcard to requestBody content.7. Fix CreateTaskRequest required name field
components/schemas/actor-tasks/CreateTaskRequest.yamlmust have required property 'name'at POST/v2/actor-tasksnamefield was marked as required, but the API auto-generates a name when not provided. OnlyactIdis truly required.8. Fix missing waitForFinish parameter on actors runs/last
paths/actors/acts@{actorId}@runs@last.yaml:63Unknown query parameter 'waitForFinish'at GET/v2/acts/{actorId}/runs/lastrouteToLastRunRoutes(), but the spec was missing thewaitForFinishRunparameter declaration.9. Fix webhook requestUrl format:uri
components/schemas/webhooks/WebhookCreate.yamlmust match format "uri"at POST/v2/webhooksWEBHOOK_REQUEST_URL_REGEXPthat accepts URLs not matching strictformat: urivalidation. Removedformat: uri.10. Fix request queue payload type
components/schemas/request-queues/RequestBase.yamlmust be object,nullat POST/v2/request-queues/{queueId}/requests[object, "null"]but the API also accepts string payloads (String|Buffer|Object). Changed to[string, object, "null"].11. Fix status query parameter serialization
components/parameters/runAndBuildParameters.yaml(status parameter)Parameter 'status' must be url encoded. Its value may not contain reserved characters.at GET/v2/acts/{actorId}/runs?status=SUCCEEDED,FAILEDstatusparameter accepts comma-separated values parsed byparseCommaSeparatedString(). Changed totype: arraywithitems: {type: string}andexplode: falsefor proper comma-separated serialization.12. Fix filter query parameter serialization
paths/request-queues/request-queues@{queueId}@requests.yaml(filter parameter)Parameter 'filter' must be url encoded. Its value may not contain reserved characters.at GET/v2/request-queues/{queueId}/requests?filter=locked,pendingfilterparameter accepts comma-separated values parsed byfilterString?.split(','). Changed totype: arraywithitems: {type: string, enum: [locked, pending]}andexplode: false.13. Fix startedAfter/startedBefore reserved character handling
components/parameters/runAndBuildParameters.yaml(startedAfter & startedBefore)Parameter 'startedAfter' must be url encoded. Its value may not contain reserved characters./ same forstartedBeforeallowReserved: trueto both parameters.14. Fix missing waitForFinish parameter on actor-tasks runs/last
paths/actor-tasks/actor-tasks@{actorTaskId}@runs@last.yaml:55Unknown query parameter 'waitForFinish'at GET/v2/actor-tasks/{actorTaskId}/runs/lastrouteToLastRunRoutes()function, forwarding all query parameters including waitForFinish.15. Fix Content-Encoding enum missing identity value
components/objects/key-value-stores/key-value-stores@{storeId}@records@{recordKey}put.yaml:13must be equal to one of the allowed values: gzip, deflate, brat PUT/v2/key-value-stores/{storeId}/records/{recordKey}identityencoding for HTML records, checked atrecord.ts:102against[...RECORD_SUPPORTED_ENCODING_TYPES_LIST, IDENTITY_ENCODING_TYPE]. Theidentityvalue was missing from the spec's Content-Encoding enum.16. Fix missing view parameter on run-sync-get-dataset-items endpoints
paths/actors/acts@{actorId}@run-sync-get-dataset-items.yaml,paths/actor-tasks/actor-tasks@{actorTaskId}@run-sync-get-dataset-items.yaml,components/parameters/datasetItemsParameters.yamlUnknown query parameter 'view'at POST/v2/acts/{actorId}/run-sync-get-dataset-itemsand POST/v2/actor-tasks/{actorTaskId}/run-sync-get-dataset-itemsrespondWithDatasetItemsStream()which processes theviewquery parameter (atdatasets.ts:328), but the spec was missing this parameter. Also extracted the inlineviewparameter fromdatasets@{datasetId}@items.yamlinto a reusable component indatasetItemsParameters.yaml.Refactoring
Extract view parameter to reusable component
components/parameters/datasetItemsParameters.yaml,paths/datasets/datasets@{datasetId}@items.yamlviewparameter definition from the dataset items endpoint into the shareddatasetItemsParameters.yamlfile. This follows the apify-docs guideline to prefer$refreuse over duplication, and enables the fix for run-sync-get-dataset-items endpoints.Replace single-item enums with const
components/schemas/actor-pricing-info/PayPerEventActorPricingInfo.yaml:11,FreeActorPricingInfo.yaml:10,PricePerDatasetItemActorPricingInfo.yaml:12,FlatPricePerMonthActorPricingInfo.yaml:12,paths/request-queues/request-queues@{queueId}@requests@batch.yaml:95enumdefinitions withconst(OpenAPI 3.1 syntax). Affects 4 pricingModel discriminator fields and 1 Content-Type header parameter.AGENTS.md syntax hints update
AGENTS.mdcb46377) addingconstsyntax hints to the OpenAPI specification changes section. This motivated the enum-to-const refactoring above.Unfixed errors
False positives
Nullable date-time fields in taggedBuilds
must be null/must match a schema in anyOfat response path/response/data/taggedBuilds/lateston GET/PUT/v2/acts/{actorId}(~193 occurrences)TaggedBuildInfo.yamlschema hasfinishedAt: type: [string, "null"], format: date-time. The express-openapi-validator incorrectly rejects null values for date-time formatted fields. This is a known validator limitation.Nullable date-time fields in lastDispatch
must be null/must match a schema in anyOfat response path/response/data/lastDispatchon PUT/v2/webhooks/{webhookId}(~9 occurrences)ExampleWebhookDispatch.yamlhasfinishedAt: type: [string, "null"], format: date-time.Handler mutation of input field (anyOf)
must be object/must be null/must match a schema in anyOfat request path/body/inputon POST/v2/actor-tasksand POST/v2/schedules(~8 occurrences)JSON.stringify(req.body.input)before the OpenAPI validator runs (validator is in monitoring-only mode viares.on('finish', ...)), turning the object into a string. The validator then sees a string where it expects object|null.KV store record oneOf response validation
must match exactly one schema in oneOfat response path/responseon GET/v2/key-value-stores/{storeId}/records/{recordKey}(~341 occurrences)Out of scope errors
Missing actor-runs sub-route endpoints
not foundat GET/v2/actor-runs/{runId}/log,/v2/actor-runs/{runId}/dataset,/v2/actor-runs/{runId}/key-value-store,/v2/actor-runs/{runId}/request-queue(~277 occurrences)Missing validate-input endpoint
not foundat POST/v2/acts/{actorId}/validate-input(~46 occurrences)Global method query parameter override
Unknown query parameter 'method'on various endpoints (~4 occurrences)overrideMethodMiddleware) that accepts?method=on every endpoint to override the HTTP method. This is a cross-cutting transport-level concern, not a per-endpoint parameter. Not appropriate to add to every endpoint in the spec.Potential API bugs
None identified.
Issues
Partially implements: #2286